package brainbots;


import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import brainbots.bot.BrainBot;
import brainbots.brains.Brain;

/*The document in MVC model*/
public class World{

    public ArrayList<BrainBot> bots = new ArrayList<BrainBot>();
    public ArrayList<Food> food = new ArrayList<Food>();
    public WorldCells<BrainBot> cells;//cells for fast access to the bots and other objects
    public WorldCells<Food> foodCells;//cells for fast access to the bots and other objects
    
    /**pool geometry*/
    private double x0,y0,x1,y1;
    private double cellUpdateInterval = 0.5;
    private double timeAfterCellUpdate = 0.0;
	private AbstractBreeder breeder;
    
    public void setCellUpdateInterval(double dt)
    {
        cellUpdateInterval = dt;
    }
    public World()
    {
        x0 = 0;
        y0 = 0;
        x1 = 800;
        y1 = 600;
        
        cells = new WorldCells<BrainBot>(x0,y0,x1,y1,12,10);//100x100 cells. It should be much enough. In theory, best result should be achieved, when number of cells near to the number of bots and cells are square.
        foodCells = new WorldCells<Food>(x0,y0,x1,y1,12,10);
        
    }
    

    public AbstractBreeder getBreeder() {
		return breeder;
	}
    
    /**Simulate all bots*/
    public void simulate(double dt){

    	LinkedList<BrainBot> deadBotsQueue = new LinkedList<BrainBot>();
    	
    	Iterator<BrainBot> iter;
    	for (iter = bots.iterator(); iter.hasNext();){
    		BrainBot bot = iter.next();
            bot.simulate(dt);
            if (bot.getEnergy()<=0){//bot is going to die!
            	iter.remove();//remove the bot from the array
            	cells.remove(bot);
            	deadBotsQueue.add(bot);
            }
            applyRestrictions(bot);
        }
        
        timeAfterCellUpdate += dt;
        if (timeAfterCellUpdate> cellUpdateInterval){
            cells.updateBots();
            timeAfterCellUpdate = 0.0;
        }
        //process dead bots
    	for (BrainBot b: deadBotsQueue){
    		onBotDeath(b);
    	}

    }
    /** called when bot is died and removed from list*/
    private void onBotDeath(BrainBot bot) {
    	if (breeder != null){
    		breeder.onBotDeath(bot);
    	}
	}
	/**do not let bots go outside the allowed area*/
    private void applyRestrictions(BrainBot b){
        if (b.getX() < x0 && b.getVX() < 0){
            b.setVX(-b.getVX());
            b.setX(x0);
        }else
        if (b.getX() > x1 && b.getVX() > 0){
            b.setVX(-b.getVX());
            b.setX(x1);
        }
        
        if (b.getY() < y0 && b.getVY() < 0){
            b.setVY(-b.getVY());
            b.setY(y0);
        }else
        if (b.getY() > y1 && b.getVY() > 0){
            b.setVY(-b.getVY());
            b.setY(y1);
        }
    }
    
    public void addBot(BrainBot b){
        b.setWorld(this);
        bots.add(b);
        cells.putItem(b);//put bot to the fast access array;
    }
    
    /**Create bot instance for given brain and add it to the pool*/
    public BrainBot addBot(double x, double y, double alpha, Brain brain){
        BrainBot bot = new BrainBot(x,y,alpha);
        bot.setBrain(brain);
        addBot(bot);
        return bot;
    }
    
    /**Pick bot reference by coordinates*/
    public BrainBot pickBot(double x, double y)
    {
        final BrainBot[] found = new BrainBot[]{null};
        
        cells.inCircle(x,y,30,new ItemEnumerator<BrainBot>(){
            public boolean item(BrainBot b){
                found[0]=b;
                return true;
            }
        });
        return found[0];
    }
    
    public void addFood(Food f){
        food.add(f);
        foodCells.putItem(f);
    }
    public Food addRandomFood(){
        Food f = new Food(Math.random()*(x1-x0)+x0, Math.random()*(y1-y0)+y0);
        addFood(f);
        return f;
    }
    public void removeFood(Food f){
        food.remove(food.indexOf(f));
        foodCells.remove(f);
    }
    
    /**Attach the breeder object that controls bot population*/
    public void setBreeder(AbstractBreeder b){
    	breeder = b;
    	b.setWorld(this);
    }
	public double getWidth() {
		return x1-x0;
	}
	public double getHeight(){
		return y1 - y0;
	}
	public double getX0(){
		return x0;
	}
	public double getY0(){
		return y0;
	}
	public void eatFood(BrainBot brainBot, Food foodToEat) {
		if (breeder != null)
			breeder.onFoodEaten(foodToEat);
		removeFood(foodToEat);
	}
    
}
